﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XMLSpreadGear.Core;
using XMLSpreadGear.Headers;
using System.Xml;
using XMLSpreadGear.Templates;
using XMLSpreadGear.Attributes;
using System.Reflection;
using System.Xml.Linq;
using System.Globalization;

namespace XMLSpreadGear.ExcelEngine
{
    /// <summary>
    /// This is the main class used for transforming data into SpreadsheetML
    /// </summary>
    public class ExcelProcessor : Processor
    {
        private int maxcolumncount;
        private int maxrowscount;
        private string strformula = "={0}(R[-{1}]C:R[-1]C)";

        /// <summary>
        /// Its constructor takes styles as a parameter.  
        /// There are several default styles are included in the XMLSpreadGear.  
        /// Those styles can be access using ExcelStyles.BluePresentationStyle.Style or 
        /// ExcelStyles.GrayPresentationStyle.Style
        /// </summary>
        /// <param name="styles">object os Styles class.</param>
        public ExcelProcessor(Styles styles)
            : base(styles)
        {
            maxcolumncount = 0;
            maxrowscount = 0;
        }


        protected override void CreateProcessingInstructions()
        {
            base.CreateProcessingInstructions();
            this.XmlDoc.AppendChild(this.XmlDoc.CreateProcessingInstruction("mso-application", "progid='Excel.Sheet'"));
        }

        private XmlDocument DefineTable()
        {
            Table table = new Table("Table");
            return table.GetElement();
        }

        /// <summary>
        /// Name of the worksheet
        /// </summary>
        public string WorksheetName { get; set; }

        /// <summary>
        /// Name of the Author
        /// </summary>
        public string AuthorName { get; set; }

        protected override void CreateHeaderTemplate()
        {
            Workbook workbook = new Workbook("Workbook");
            workbook.DocumentProperties = new DocumentProperties("DocumentProperties", AuthorName, AuthorName, string.Empty, string.Empty);
            workbook.ExcelWorkbook = new ExcelWorkbook("ExcelWorkbook", 1000, 1500, 100, 100, false, false);
            workbook.Styles = this.Styles;
            workbook.Worksheet = new Worksheet("Worksheet", WorksheetName, false, false);
            this.XmlDoc.AppendChild(this.XmlDoc.ImportNode(workbook.GetElement().FirstChild, true));

            XmlNode worksheetnode = XmlDoc.GetElementsByTagName("Worksheet").OfType<XmlNode>().FirstOrDefault();
            worksheetnode.InnerXml = DefineTable().InnerXml;
        }

        protected override void ProcessJob(Job jobToProcess)
        {
            XmlNode tablenode = XmlDoc.GetElementsByTagName("Table").OfType<XmlNode>().FirstOrDefault();
            if (tablenode != null)
            {
                if (jobToProcess.IsHaderJob)
                {
                    Row headerrow = new Row("Row");
                    headerrow.Cell = new Cell("Cell");
                    headerrow.Caption = "header";

                    if (jobToProcess.BeginGroup)
                    {
                        headerrow.Index = (ulong) maxrowscount + 3;
                        maxrowscount = maxrowscount + 3;
                    }

                    Data headerdata = new Data("Data", DataType.String);
                    headerdata.Value = jobToProcess.HeaderString;

                    headerrow.Cell.Add(new Cell("Cell") { StyleID = jobToProcess.HeaderStyleID, Data = headerdata });

                    tablenode.InnerXml = tablenode.InnerXml + headerrow.ToString();
                    maxrowscount++;
                }
                else
                {
                    if (jobToProcess.DataObject == null)
                        throw new InvalidOperationException("DataObject can not be null");

                    if (jobToProcess.DataObject.OfType<object>().Count() <= 0)
                        throw new InvalidOperationException("DataObject count should be greater than zero");

                    object firstitem = jobToProcess.DataObject.OfType<object>().First();

                    PropertyInfo[] props = (from objprop in firstitem.GetType().GetProperties()
                                            where objprop.GetCustomAttributes(typeof(CellTemplateAttribute), false).Count() > 0
                                            select objprop).ToArray<PropertyInfo>();

                    if (props.Count() <= 0)
                        throw new InvalidOperationException("None of properties of DataObject decorated with CellTemplateAttribute");


                    if (props.Count() > maxcolumncount)
                    {
                        DefineColumns(props, maxcolumncount);
                    }
                                       

                    maxcolumncount = ((props.Count() > maxcolumncount) ? props.Count() : maxcolumncount);

                    if (string.IsNullOrEmpty(jobToProcess.HeaderString))
                    {
                        tablenode.InnerXml = tablenode.InnerXml + DefineHeaderRow(props, jobToProcess.BeginGroup);
                    }
                    else
                    {
                        tablenode.InnerXml = tablenode.InnerXml + DefineTableHeading(jobToProcess);
                        tablenode.InnerXml = tablenode.InnerXml + DefineHeaderRow(props, false);
                    }

                    XNamespace ss = "urn:schemas-microsoft-com:office:spreadsheet";
                    XElement doc = XElement.Parse(tablenode.OuterXml);

                    XElement rows = new XElement(ss + "Rows",
                        from objdata in jobToProcess.DataObject.OfType<object>()
                        select new XElement(ss + "Row",
                            from objprop in props
                            select new XElement(ss + "Cell",
                                from objattr in objprop.GetCustomAttributes(typeof(CellTemplateAttribute), false)
                                select new XAttribute(ss + "StyleID",
                                    ((CellTemplateAttribute)objattr).CellStyleID),
                                new XElement(ss + "Data",
                                    new XAttribute(ss + "Type", objprop.MapPropertyType()),
                                    objprop.GetValue(objdata,null)))));


                    int formulacolcount = (from objprop in props
                                           where (from objatr in objprop.GetCustomAttributes(typeof(CellTemplateAttribute), false)
                                                  where ((CellTemplateAttribute)objatr).Formula != FormulaType.NONE
                                                  select objatr).Count() > 0
                                           select objprop).Count();

                    if (formulacolcount > 0)
                    {
                        XElement formularow = new XElement(ss + "Row");
                        foreach (PropertyInfo prop in props)
                        {
                            XElement formulacell = new XElement(ss + "Cell");
                            if (prop.GetCustomAttributes(typeof(CellTemplateAttribute), false).Count() > 0)
                            {
                                CellTemplateAttribute cellattr = (CellTemplateAttribute)prop.GetCustomAttributes(typeof(CellTemplateAttribute), false).First();

                                XAttribute styleattr = new XAttribute(ss + "StyleID", cellattr.StyleID);

                                formulacell.Add(styleattr);

                                if (cellattr.Formula != FormulaType.NONE)
                                {
                                    XAttribute formulaattr = new XAttribute(ss + "Formula",
                                        string.Format(strformula, Enum.GetName(typeof(FormulaType), cellattr.Formula),
                                        jobToProcess.DataObject.OfType<object>().Count()));

                                    formulacell.Add(formulaattr);
                                }

                            }

                            formularow.Add(formulacell);
                        }
                        rows.Add(formularow);
                    }
                    //(DataType)Enum.Parse(typeof(DataType), objprop.PropertyType.Name, true)
                    doc.Add(rows.Elements());
                    XmlNode worksheetnode = XmlDoc.GetElementsByTagName("Worksheet").OfType<XmlNode>().FirstOrDefault();
                    worksheetnode.InnerXml = doc.ToString(SaveOptions.DisableFormatting);

                    maxrowscount = maxrowscount + jobToProcess.DataObject.OfType<object>().Count();
                }
            }
        }

        /// <summary>
        /// This method initiates the transformation process
        /// </summary>
        public override void Process()
        {
            base.Process();

            XmlNodeList headernodelist = this.XmlDoc.SelectNodes(@"/Workbook/ss:Worksheet/ss:Table/ss:Row[@ss:Caption='header']", GetNamespaceManager());

            foreach (XmlNode headernode in headernodelist)
            {
                XmlNode lastcellnode = headernode.LastChild;

                if (lastcellnode != null)
                {
                    XmlAttribute mergeattribute = this.XmlDoc.CreateAttribute(Contstants.SpredsheetAttributePrefix, "MergeAcross", Contstants.SpredsheetAttributeNamespace);
                    mergeattribute.Value = (maxcolumncount-1).ToString();
                    lastcellnode.Attributes.Append(mergeattribute);
                }
            }

            OnProcessCompletion();
            
        }

        private XmlNamespaceManager GetNamespaceManager()
        {
            XmlNamespaceManager namespacemanager = new XmlNamespaceManager(this.XmlDoc.NameTable);
            namespacemanager.AddNamespace("ss", "urn:schemas-microsoft-com:office:spreadsheet");
            return namespacemanager;
        }

        private string DefineRows(object objitem, PropertyInfo[] props)
        {
            Row newrow = new Row("Row");
            newrow.AutoFitHeight = true;
            newrow.Cell = new Cell("Cell");
            foreach (PropertyInfo property in props)
            {
                Data datatype = new Data("Data", (DataType)Enum.Parse(typeof(DataType), property.PropertyType.Name, true));

                datatype.Value = ((property.GetValue(objitem, null) != null) ? property.GetValue(objitem, null).ToString() : string.Empty);

                Cell cell = new Cell("Cell");
                cell.Data = datatype;

                object[] objattrs = property.GetCustomAttributes(typeof(CellTemplateAttribute), false);
                CellTemplateAttribute cellattribute = objattrs.OfType<CellTemplateAttribute>().FirstOrDefault();
                if (cellattribute != null)
                {
                    cell.StyleID = cellattribute.CellStyleID;
                }
                newrow.Cell.Add(cell);
            }


            return newrow.ToString();
        }

        private string DefineTableHeading(Job jobToProcess)
        {
            Row headerrow = new Row("Row");
            headerrow.Cell = new Cell("Cell");
            headerrow.Caption = "header";

            if (jobToProcess.BeginGroup)
            {
                headerrow.Index = (ulong)maxrowscount + 3;
                maxrowscount = maxrowscount + 3;
            }

            Data headerdata = new Data("Data", DataType.String);
            headerdata.Value = jobToProcess.HeaderString;

            headerrow.Cell.Add(new Cell("Cell") { StyleID = jobToProcess.HeaderStyleID, Data = headerdata });

            maxrowscount++;

            return headerrow.ToString();
        }

        private string DefineHeaderRow(PropertyInfo[] props, bool changeIndex)
        {
            Row newrow = new Row("Row");
            if (changeIndex)
            {
                newrow.Index = (ulong) maxrowscount + 3;
                maxrowscount = maxrowscount + 3;
            }
            newrow.AutoFitHeight = true;
            newrow.Cell = new Cell("Cell");
            for (int i = 0; i < props.Count(); i++)
            {
                PropertyInfo info = props[i];

                object[] objattrs = info.GetCustomAttributes(typeof(CellTemplateAttribute), false);

                CellTemplateAttribute cellattribute = objattrs.OfType<CellTemplateAttribute>().FirstOrDefault();

                if (cellattribute != null)
                {
                    Data datatype = new Data("Data", DataType.String);
                    datatype.Value = cellattribute.Caption;

                    Cell cell = new Cell("Cell");
                    cell.Data = datatype;
                    cell.StyleID = cellattribute.StyleID;
                    newrow.Cell.Add(cell);
                }
            }
            maxrowscount++;
            return newrow.ToString();
        }

        private void DefineColumns(PropertyInfo[] props, int iterator)
        {
            XmlNode node = this.XmlDoc.GetElementsByTagName("Table").OfType<XmlNode>().FirstOrDefault();
            if (node != null)
            {
                //string backupxml = node.InnerXml;
                //node.InnerXml = string.Empty;
                for (int i = iterator; i < props.Count(); i++)
                {
                    PropertyInfo info = props[i];
                    object[] objattrs = info.GetCustomAttributes(typeof(CellTemplateAttribute), false);

                    CellTemplateAttribute cellattribute = objattrs.OfType<CellTemplateAttribute>().FirstOrDefault();
                    
                    Column col = new Column("Column");
                    col.AutoFitWidth = true;
                    col.Hidden = false;
                    if (cellattribute != null)
                    {
                        col.Width = cellattribute.Width;
                    }

                    if (GetFirstRow() == null)
                    {
                        node.InnerXml = node.InnerXml + col.ToString();
                    }
                    else
                    {
                        node.InsertBefore(this.XmlDoc.ImportNode(col.GetElement().FirstChild,true), GetFirstRow());
                    }
                }
                //node.InnerXml = node.InnerXml + backupxml;
            }
        }

        private XmlNode GetFirstRow()
        {
            XmlNodeList headernodelist = this.XmlDoc.SelectNodes(@"/Workbook/ss:Worksheet/ss:Table/ss:Row", GetNamespaceManager());
            if (headernodelist.Count > 0)
            {
                return headernodelist[0];
            }
            else
            {
                return null;
            }
        }
    }
}
